﻿using MultiLang;
using System;
using System.Collections.Generic;
using System.Linq;

using JetBrains.Annotations;

using Rsdn.SmartApp;

namespace Rsdn.Janus
{
    [Service(typeof(ICommandHandlerService))]
    public sealed class CommandHandlerService : ICommandHandlerService
    {
        private readonly ICommandService _commandService;
        private readonly ICommandTarget[] _commandTargets;

        public CommandHandlerService([NotNull] IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");   //MLHIDE

            _commandService = serviceProvider.GetRequiredService<ICommandService>();
            _commandTargets =
                new ExtensionsCache<CommandTargetInfo, ICommandTarget>(serviceProvider).GetAllExtensions();
        }

        #region Implementation of ICommandHandlerService

        public void ExecuteCommand(
            [NotNull] string commandName,
            [NotNull] ICommandContext context)
        {
            if (commandName == null)
                throw new ArgumentNullException("commandName");       //MLHIDE
            if (context == null)
                throw new ArgumentNullException("context");           //MLHIDE

            CheckCommandAndParameters(commandName, context);

            var isAvaliable = false;
            foreach (var target in _commandTargets)
            {
                if (target.QueryStatus(commandName, context) == CommandStatus.Normal)
                {
                    target.Execute(commandName, context);
                    isAvaliable = true;
                }
            }

            if (!isAvaliable)
            {
                throw new InvalidOperationException(
                    ml.ml_string(8, "Команда не может быть выполнена, так как этого не позволяет её статус."));
            }
        }

        /// <summary>
        /// Получить статус команды.
        /// Поиск во всех возможных классах комманд
        /// </summary>
        /// <param name="commandName">имя команды</param>
        /// <param name="context">контекст команды</param>
        /// <returns>статус команды</returns>
        public CommandStatus QueryStatus(
            [NotNull] string commandName,
            [NotNull] ICommandContext context)
        {
            if (commandName == null)
            {
                throw new ArgumentNullException("commandName");       //MLHIDE
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");           //MLHIDE
            }

            CheckCommandAndParameters(commandName, context);

            var disabled = false;
            #region Commands samples
            // 20 targets by init
            //---LogCommandTarget
            // [0] Janus.Log.Show
            // [1] Janus.Log.Close
            // [2] Janus.Log.Clear
            // ---ForumsCommandTarget
            // [ 0] Janus.Forum.OpenFileUploadOnRsdn"
            // [ 1] Janus.Forum.SetMessagesReadMarkByDate"
            // [ 2] Janus.Forum.ShowAllArticles"
            // [ 3] Janus.Forum.ShowAllFaq"
            // [ 4] Janus.Forum.ShowTeamList"
            // [ 5] Janus.Forum.TopicDownload"
            // [ 6] Janus.Forum.ShowUserRatingIn"
            // [ 7] Janus.Forum.ShowUserRatingOut"
            // [ 8] Janus.Forum.StateExport"
            // [ 9] Janus.Forum.StateImport"
            // ---TagLineCommandTarget
            // [ 0] Janus.Forum.TagLine.Add
            // [ 1] Janus.Forum.TagLine.Edit
            // [ 2] Janus.Forum.TagLine.Delete
            // --- DatabaseCommandTarget
            // [ 0] Janus.Database.DbMigration
            // [ 1] Janus.Database.RebuildAggregates
            // ---SearchCommandTarget
            // [ 0] Janus.Search.BuildSearchIndex
            // [ 1] Janus.Search.ClearSearchResult
            // ---ForumCommandTarget
            // [ 0] Janus.Forum.OpenForumOnRsdn
            // [ 1] Janus.Forum.ShowForumArticles
            // [ 2] Janus.Forum.ShowForumFaq
            // [ 3] Janus.Forum.WriteMessage
            // [ 4] Janus.Forum.SetAllMessagesInForumReadMark
            // [ 5] Janus.Forum.MessageExport
            // ---MessageEditorCommandTarget
            // [ 0] Janus.Forum.MessageEditor.Send
            // [ 1] Janus.Forum.MessageEditor.Preview
            // [ 2] Janus.Forum.MessageEditor.Save
            // [ 3] Janus.Forum.MessageEditor.SaveAndClose
            // [ 4] Janus.Forum.MessageEditor.Undo
            // [ 5] Janus.Forum.MessageEditor.Redo
            // [ 6] Janus.Forum.MessageEditor.FindAndReplace
            // [ 7] Janus.Forum.MessageEditor.InsertSmile
            // [ 8] Janus.Forum.MessageEditor.InsertSingleTag
            // [ 9] Janus.Forum.MessageEditor.InsertPairTag
            // [10] Janus.Forum.MessageEditor.Close
            // ---ApplicationCommandTarget
            // [ 0] Janus.Application.Exit
            // [ 1] Janus.Application.About
            // [ 2] Janus.Application.Help
            // [ 3] Janus.Application.SendBugReport
            // [ 4] Janus.Application.UserOptions
            // [ 5] Janus.Application.AppOptions
            // [ 6] Janus.Application.ShowMainForm
            //---Rsdn.Janus.ForumViewerCommandTarget
            // [0] Janus.Forum.GoToMessage
            // [ 1] Janus.Forum.GoToMessageWithPromt
            // [ 2] Janus.Forum.SmartJump
            // [ 3] Janus.Forum.CollapseTopicAndSelectRoot
            // [ 4] Janus.Forum.ExpandUnreadBranches
            // [ 5] Janus.Forum.SelectMessageByAttribute
            // [ 6] Janus.Forum.NavigateBackward
            // [ 7] Janus.Forum.NavigateForward
            // [ 8] Janus.Forum.LoadAllMessagesInForum
            //---NavigationCommandTarget
            // [ 0] Janus.Navigation.SelectNextFeature
            // [ 1] Janus.Navigation.SelectPreviousFeature
            // [ 2] Janus.Navigation.SelectFeatureByKey
            // [ 3] Janus.Navigation.ToggleNavigationWindowVisibility
            // ---SynchronizationCommandTarget
            // [ 0] Janus.Synchronization.Synchronize
            // ---ForumMessageCommandTarget
            // [ 0] Janus.Forum.AddForumMessageToFavorites
            // [ 1] Janus.Forum.CopyMessageAddress
            // [ 2] Janus.Forum.CopyMessageAuthorAddress
            // [ 3] Janus.Forum.OpenMessageInJBrowser
            // [ 4] Janus.Forum.OpenMessageOnRsdn
            // [ 5] Janus.Forum.OpenMessageRatingOnRsdn
            // [ 6] Janus.Forum.Moderating
            // [ 7] Janus.Forum.OpenModeratingOnRsdn
            // [ 8] Janus.Forum.ShowMessageRating
            // [ 9] Janus.Forum.ReplyMessage
            // [10] Janus.Forum.RateMessage
            // [11] Janus.Forum.RegetTopic
            // [12] Janus.Forum.SetTopicReadMark
            // [13] Janus.Forum.SetMessagesReadMark
            // [14] Janus.Forum.SetMessagesRepliesAutoReadMark
            //---FavoritesCommandTarget
            // [ 0] Janus.Favorites.CreateFolder
            // [ 1] Janus.Favorites.RenameFolder
            // [ 2] Janus.Favorites.AddLink
            // [ 3] Janus.Favorites.OpenLink
            // [ 4] Janus.Favorites.EditLink
            // [ 5] Janus.Favorites.MoveItem
            // [ 6] Janus.Favorites.DeleteItem
            //---OutboxCommandTarget
            // [ 0] Janus.Outbox.EditItem
            // [ 1] Janus.Outbox.DeleteItem
            //---JBrowserCommandTarget
            // [ 0] Janus.JBrowser.NavigateBackward
            // [ 1] Janus.JBrowser.NavigateForward
            // [ 2] Janus.JBrowser.Refresh
            // [ 3] Janus.JBrowser.Stop
            // [ 4] Janus.JBrowser.OpenInExternalBrowser
            // [ 5] Janus.JBrowser.Close
            //---ConsoleCommandTarget
            // [ 0] Janus.Console.Show
            // [ 1] Janus.Console.Close
            // [ 2] Janus.Console.Clear
            // [ 3] Janus.Console.CommandInfo
            // [ 4] Janus.Console.Prompt
            //---Jet.JetCommandTarget
            // [ 0] Janus.Jet.CompactDb
            //---Rsdn.Janus.Mssql.MssqlCommandTarget
            // [ 0] Janus.Mssql.CompactDb
            //---ForumCommandTarget
            // [ 0] Janus.Forum.SubscribeOrUnsubscribeForum
            // [ 1] Janus.Forum.Subscription
            // ---Sqlite.SqliteCommandTarget
            // [ 0] Janus.Sqlite.CompactDb
            // [ 1] Janus.Sqlite.UpdateStatistics
            #endregion
            foreach (var target in _commandTargets)
            {
                var status = target.QueryStatus(commandName, context);
                if (status == CommandStatus.Normal)
                {
                    return CommandStatus.Normal;
                }

                if (status == CommandStatus.Disabled)
                {
                    disabled = true;
                }
            }

            return disabled ? CommandStatus.Disabled : CommandStatus.Unavailable;
        }

        public IDisposable SubscribeCommandStatusChanged(
            IServiceProvider serviceProvider,
            EventHandler<ICommandHandlerService, string[]> handler)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("serviceProvider");   //MLHIDE
            }
            if (handler == null)
            {
                throw new ArgumentNullException("handler");           //MLHIDE
            }

            IEnumerable<IDisposable> enumerable = _commandTargets
                .Select(
                    target =>
                    target.SubscribeStatusChanged(
                        serviceProvider,
                        (sender, commansIds) => handler(this, commansIds)));

            return enumerable.Merge();
        }

        #endregion

        #region Private Members

        private void CheckCommandAndParameters(string commandName, ICommandContext context)
        {
            var commandInfo = _commandService.GetCommandInfo(commandName);
            foreach (var parameter in commandInfo.Parameters)
            {
                if (!parameter.IsOptional && !context.IsParameterExists(parameter.Name))
                {
                    throw new ApplicationException(
                        ml.ml_string(9, "Обязательный параметр '{0}' команды '{1}' отсутствует в контексте.")
                            .FormatStr(parameter.Name, commandName));
                }
            }
        }

        #endregion
    }
}